package com.wei.czz.framework.index.service.impl;

import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.wei.czz.common.constant.EntityConstant;
import com.wei.czz.common.constant.RedisConstant;
import com.wei.czz.common.dto.blog.BlogDto;
import com.wei.czz.common.dto.blog.TagSortDto;
import com.wei.czz.common.dto.blog.WebStatsDto;
import com.wei.czz.common.enums.CommonEnum;
import com.wei.czz.common.enums.blog.BlogEnum;
import com.wei.czz.common.enums.blog.BlogUserOperationEnum;
import com.wei.czz.common.enums.rabbitMQ.MqEnum;
import com.wei.czz.common.exception.CzzException;
import com.wei.czz.common.property.JwtProperty;
import com.wei.czz.common.tool.PageDto;
import com.wei.czz.common.utils.CacheKeyUtils;
import com.wei.czz.common.utils.RandomNumUtils;
import com.wei.czz.common.utils.TimeUtils;
import com.wei.czz.framework.admin.service.UserLoginService;
import com.wei.czz.framework.blog.service.*;
import com.wei.czz.framework.common.handler.ThrottleHandler;
import com.wei.czz.framework.common.handler.redis.RedisHashHandler;
import com.wei.czz.framework.common.handler.redis.RedisStringHandler;
import com.wei.czz.framework.common.handler.redis.RedisHandler;
import com.wei.czz.framework.common.handler.redis.RedisZSetHandler;
import com.wei.czz.common.vo.blog.IndexVo;
import com.wei.czz.common.dto.blog.SortBlogDto;
import com.wei.czz.framework.blog.dao.BlogContentDao;
import com.wei.czz.framework.blog.entity.BlogEntity;
import com.wei.czz.framework.common.helper.RabbitHelper;
import com.wei.czz.framework.index.service.IndexService;
import com.wei.czz.framework.rabbitMQ.message.blog.BlogOperationMessage;
import com.wei.czz.framework.blog.entity.BlogIssueEntity;
import com.wei.czz.framework.admin.entity.UserEntity;
import com.wei.czz.framework.admin.service.UserService;
import lombok.AllArgsConstructor;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.data.redis.connection.RedisZSetCommands;
import org.springframework.data.redis.core.*;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.concurrent.TimeUnit;

/**
 * Created by IntelliJ IDEA.
 *
 * @author wei
 * date: 2021-05-26 16:36:00
 * className: IndexService 浏览器页面-首页面操作请求接口实现类
 * version: 1.0
 * description:
 */
@Service("indexService")
@AllArgsConstructor
public class IndexServiceImpl implements IndexService {

    private static final Logger log = LoggerFactory.getLogger(IndexServiceImpl.class);

    private final JwtProperty jwtProperty;

    private final BlogService blogService;

    private final BlogIssueService blogIssueService;

    private final BlogContentDao blogContentDao;

    private final UserService userService;

    private final BlogTagService blogTagService;

    private final BlogCommentService blogCommentService;

    private final UserLoginService userLoginService;

    private final TagService tagService;

    private final RedisHashHandler redisHashHandler;

    private final RedisStringHandler redisStringHandler;

    private final RedisZSetHandler redisZSetHandler;

    private final RedisHandler redisHandler;

    private final RedissonClient redissonClient;

    private final RabbitHelper rabbitHelper;

    private final ThrottleHandler throttleHandler;

    @Override
    public PageDto<BlogDto> getIndexBlogList(IndexVo indexVo) {

        // 获取String临时缓存键
        String indexListKey = indexVo.getIndexListKey();
        // 获取Redis分布式锁对象
        RLock lock = redissonClient.getLock(RedisConstant.LOCK + indexListKey);
        // 查询Redis，获取博客列表缓存数据
        PageDto<BlogDto> pageDto = redisStringHandler.get(indexListKey);
        if (pageDto != null) {
            // 尝试更新缓存键的过期时间
            redisHandler.tryUpdateKeyExpire(lock, indexListKey, 150, RandomNumUtils.random5Minutes(10));
            return pageDto;
        }
        // 加锁30秒
        lock.lock(30, TimeUnit.SECONDS);

        try {
            // 重复查询Redis，获取博客列表缓存数据
            pageDto = redisStringHandler.get(indexListKey);
            if (pageDto != null) {
                return pageDto;
            }

            Page<BlogDto> page = new Page<>(indexVo.getPage(), indexVo.getLimit());
            // 分页获取浏览器首页面展示的博客列表
            blogIssueService.getIndexBlogList(page, indexVo);

            // 返回结果封装
            pageDto = new PageDto<>(page);

            // 缓存当前数据五分钟
            redisStringHandler.set(indexListKey, pageDto, RandomNumUtils.random5Minutes(10), TimeUnit.SECONDS);

            return pageDto;
        } finally {
            // 判断当前锁对象是否还持有锁 并 持有该锁的线程与当前业务线程一致
            if (lock.isLocked() && lock.isHeldByCurrentThread()) {
                // 解锁
                lock.unlock();
            }
        }
    }

    @Override
    public List<SortBlogDto> getSortBlogList(BlogEnum blogEnum) {

        String sortListKey = RedisConstant.SORT_BLOG_LIST + RedisConstant.SPLIT + blogEnum.getValue();

        // 查询Redis，获取对应类型的排序博客列表数据
        List<SortBlogDto> sortBlogList = redisStringHandler.get(sortListKey);
        if (sortBlogList != null) {
            // 尝试更新缓存键的过期时间
            redisHandler.tryUpdateKeyExpire(sortListKey, 150, RandomNumUtils.random5Minutes(10));
            return sortBlogList;
        }
        // 获取Redis分布式锁对象
        RLock lock = redissonClient.getLock(RedisConstant.LOCK + sortListKey);
        // 加锁30秒
        lock.lock(30, TimeUnit.SECONDS);

        try {
            // 重复查询Redis，获取对应类型的排序博客列表数据
            sortBlogList = redisStringHandler.get(sortListKey);
            if (sortBlogList != null) {
                return sortBlogList;
            }

            if (BlogEnum.HOT_BLOG == blogEnum) {
                /*
                    前十个热门的博客
                 */
                sortBlogList = blogIssueService.getTopTenHotBlogList(CommonEnum.ZERO.getValue());
            } else if (BlogEnum.TO_DAY_BLOG == blogEnum) {
                /*
                    前十个今日发布的博客
                 */
                // 获取系统时间所在日的开始时间
                Date startToDay = TimeUtils.startToDay();
                Date endTime = new Date();
                sortBlogList = blogIssueService.getTopTenToDayBlogList(startToDay, endTime);
            } else if (BlogEnum.ELITE_BLOG == blogEnum) {
                /*
                    前十个系统推荐的博客
                 */
                sortBlogList = blogIssueService.getTopTenEliteBlogList(CommonEnum.ZERO.getValue());
            } else {
                sortBlogList = new ArrayList<>();
            }

            // 缓存当前数据五分钟
            redisStringHandler.set(sortListKey, sortBlogList, RandomNumUtils.random5Minutes(10), TimeUnit.SECONDS);
        } finally {
            // 判断当前锁对象是否还持有锁 并 持有该锁的线程与当前业务线程一致
            if (lock.isLocked() && lock.isHeldByCurrentThread()) {
                // 解锁
                lock.unlock();
            }
        }
        return sortBlogList;
    }

    @Override
    public List<TagSortDto> getTopTenTagList() {
        // 查询Redis缓存，获取被系统已发布博客引用数量排行前十的博客标签缓存信息
        List<TagSortDto> tagSortList = redisStringHandler.get(RedisConstant.TAG_LIST);
        if (tagSortList != null) {
            throttleHandler.throttleRun(() -> {
                // 尝试更新缓存键的过期时间
                redisHandler.tryUpdateKeyExpire(RedisConstant.TAG_LIST, 150, RandomNumUtils.random5Minutes(10));
            }, RedisConstant.TAG_LIST, 1500, TimeUnit.MILLISECONDS);
            return tagSortList;
        }
        // 获取Redis分布式锁对象
        RLock lock = redissonClient.getLock(RedisConstant.LOCK + RedisConstant.TAG_LIST);
        // 加锁30秒
        lock.lock(30, TimeUnit.SECONDS);

        try {
            // 重复查询Redis缓存，获取被系统已发布博客引用数量排行前十的博客标签缓存信息
            tagSortList = redisStringHandler.get(RedisConstant.TAG_LIST);
            if (tagSortList != null) {
                return tagSortList;
            }

            // 查询Redis缓存，获取被系统已发布博客引用数量排行前十的博客标签id
            Set<ZSetOperations.TypedTuple<String>> typedTuples =
                    redisZSetHandler.reverseRangeWithScores(RedisConstant.TAG_ZSET, 0, 9);
            if (!typedTuples.isEmpty()) {
                tagSortList = new ArrayList<>();
                for (ZSetOperations.TypedTuple<String> typedTuple : typedTuples) {
                    String tagName = typedTuple.getValue();
                    Double score = typedTuple.getScore();
                    if (tagName == null || score == null) {
                        throw new CzzException("标签排序对象为空，请联系管理员处理!");
                    }
                    TagSortDto tagSortDto = new TagSortDto();
                    // 设置标签id和标签关联博客数量属性值
                    tagSortDto.setTagName(tagName)
                            .setNum(score.intValue());
                    // 收集标签排序信息
                    tagSortList.add(tagSortDto);
                }

                // 缓存当前数据五分钟
                redisStringHandler.set(RedisConstant.TAG_LIST, tagSortList, RandomNumUtils.random5Minutes(10),
                        TimeUnit.SECONDS);
            }
        } finally {
            // 判断当前锁对象是否还持有锁 并 持有该锁的线程与当前业务线程一致
            if (lock.isLocked() && lock.isHeldByCurrentThread()) {
                // 解锁
                lock.unlock();
            }
        }
        return tagSortList;
    }

    @Override
    public WebStatsDto getWebStats() {

        // 查询Redis缓存，获取系统的统计信息
        WebStatsDto webStatsDto = redisStringHandler.get(RedisConstant.WEB_STATS);
        if (webStatsDto != null) {
            // 尝试更新缓存键的过期时间
            redisHandler.tryUpdateKeyExpire(RedisConstant.WEB_STATS, 60, RandomNumUtils.random2Minutes(10));
            return webStatsDto;
        }
        // 获取Redis分布式锁对象
        RLock lock = redissonClient.getLock(RedisConstant.LOCK + RedisConstant.WEB_STATS);
        // 加锁30秒
        lock.lock(30, TimeUnit.SECONDS);

        try {
            // 重复查询Redis缓存，获取系统的统计信息
            webStatsDto = redisStringHandler.get(RedisConstant.WEB_STATS);
            if (webStatsDto != null) {
                return webStatsDto;
            }
            webStatsDto = new WebStatsDto();

            // 发布的博客数量
            Long size = redisHashHandler.size(CacheKeyUtils.getRedisHashKey(EntityConstant.BLOG));
            if (size == 0)  {
                // 查询数据库系统已发布博客表，获取已发布的博客数量
                size = blogIssueService.getBlogIssueCount(null);;
            }
            // 系统全部博客点赞数
            Long like = redisStringHandler.increment(RedisConstant.LIKE_COUNT, 0);
            if (like == 0) {
                // 查询数据库用户操作博客表，统计系统已发布博客的点赞数量
                like = blogIssueService.getIssueBlogLikeNum();
                //
                redisStringHandler.set(RedisConstant.LIKE_COUNT, like);
            }
            // 系统全部评论数量
            Long comment = redisStringHandler.increment(RedisConstant.COMMENT_COUNT, 0);
            if (comment == 0) {
                comment = blogCommentService.getBlogCommentCount(null);
                //
                redisStringHandler.set(RedisConstant.COMMENT_COUNT, comment);
            }
            // 系统全部浏览数量
            Long view = redisStringHandler.increment(RedisConstant.VIEW_COUNT, 0);
            if (view == 0) {
                // 获取系统已发布博客的浏览次数总计
                view = blogIssueService.getIssueBlogViewNum();
                //
                redisStringHandler.set(RedisConstant.VIEW_COUNT, view);
            }
            // 系统全部普通用户数量
            Long user = redisHashHandler.size(CacheKeyUtils.getRedisHashKey(EntityConstant.USER));
            if (user == 0) {
                // 查询数据库系统用户表，获取所有用户的数量
                user = userService.getUserCount();
            }
            // 获取毫秒时间数
            long startTime = System.currentTimeMillis();

            RedisZSetCommands.Range range = new RedisZSetCommands.Range();
            range.gt(startTime);
            Long userOnLine = redisZSetHandler.count(RedisConstant.OVER_TIME_ZSET, range);
            if (userOnLine == 0) {
                // 查询数据库用户历史登录表，统计在线用户数
                userOnLine = userLoginService.lineCount();
            }
            // 设置系统已发布的博客数量、已发布博客的总点赞数、已发布博客的评论数量、已发布博客的浏览数量、系统普通用户数量和系统在线人数属性值
            webStatsDto.setIssueBlogNum(size)
                    .setAllBlogLikeNum(like)
                    .setAllBlogCommentNum(comment)
                    .setAllBlogViewNum(view)
                    .setCommonUserNum(user)
                    .setUserOnLineNum(userOnLine);

            // 操作Redis缓存，缓存网站统计数据两分钟
            redisStringHandler.set(RedisConstant.WEB_STATS, webStatsDto, RandomNumUtils.random2Minutes(10), TimeUnit.SECONDS);;
        } finally {
            // 判断当前锁对象是否还持有锁 并 持有该锁的线程与当前业务线程一致
            if (lock.isLocked() && lock.isHeldByCurrentThread()) {
                // 解锁
                lock.unlock();
            }
        }
        return webStatsDto;
    }

    @Override
    public BlogDto getBlogInfo(Long blogId) {
        /*
            获取已发布博客
         */
        BlogIssueEntity blogIssue = blogIssueService.getBlogIssue(blogId);

        /*
            获取博客
         */
        BlogEntity blog = blogService.getBlog(blogId);

        // 返回结果封装对象
        BlogDto blogDto = new BlogDto();
        // 属性值复制
        BeanUtils.copyProperties(blog, blogDto);
        // 设置用户id和发布时间属性值
        blogDto.setBlogId(blogId.toString())
                .setUserId(blog.getUserId().toString())
                .setIssueTime(blogIssue.getIssueTime());

        // 查询数据库系统博客基本表，根据博客id查询数据库获取博客正文内容
        String content = blogContentDao.getBlogContentById(blogId);
        blogDto.setContent(content);

        // 根据用户id获取用户信息
        UserEntity user = userService.get(blog.getUserId());
        // 设置博客对应的用户名称
        blogDto.setUserName(user.getUsername());

        // 根据博客id查询数据库获取博客关联所有标签名字
        List<String> tagNames = blogTagService.findNameList(blogId);
        // 设置博客关联标签名字
        blogDto.setTagNames(tagNames);

        return blogDto;
    }

    @Override
    public void addBlogView(Long userId, Long blogId) {
        BlogOperationMessage blogOperationMessage = new BlogOperationMessage();
        // 设置博客id、操作的用户id、操作类型（`用户浏览`）和变化值属性值
        blogOperationMessage.setBlogId(blogId)
                .setUpdateUserId(userId == null ? CommonEnum.ZERO.getLongValue() : userId)
                .setOptType(BlogUserOperationEnum.VIEW_OPT.getValue())
                .setNum(1);
        // 发送一条消息到MQ中，异步增加系统博客的浏览量
        rabbitHelper.send(MqEnum.USER_BLOG_OPT, blogOperationMessage);

        // 查询数据库系统已发布博客表，根据博客id判断博客是不是热门博客
        boolean isHot = blogIssueService.isHotBlog(blogId);
        if (isHot) {
            redisZSetHandler.incrementScore(RedisConstant.MONTH_BLOG_VIEW_ZSET, blogId, 1);
        }
        // 系统总浏览量加一
        redisStringHandler.increment(RedisConstant.VIEW_COUNT, 1);
    }
    
}
